﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Lib
{
    public class Server : NetworkObj
    {
        Socket socket;
        int port = 0;

        public delegate ClientInfo  AcceptFunc(Socket socket);
        public delegate ClientInfo  ReceiveFunc(Socket socket, int len);
        public delegate void        CloseFunc(Socket socket);
        public delegate void        SentFunc(Socket sock, int len);
        public delegate void        ErrorFunc(string func, string str);

        public AcceptFunc   OnAccept;
        public ReceiveFunc  OnReceive;
        public CloseFunc    OnClose;
        public SentFunc     OnSent;
        public ErrorFunc    OnError;

        public Server(int port)
        {
            this.port = port;

            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.Bind(new IPEndPoint(IPAddress.Any, port));
        }

        // Listen
        public void Listen()
        {
            socket.Listen(10);
            socket.BeginAccept(new AsyncCallback(Accept), this.socket);
        }

        // Callback for accept
        private void Accept(IAsyncResult result)
        {
            try
            {
                Socket serversock = (Socket)result.AsyncState;
                Socket client = serversock.EndAccept(result);

                if (OnAccept != null)
                {
                    ClientInfo ci = OnAccept(client);
                    if (ci != null)
                    {
                        client.BeginReceive(ci.Buffer, 0, ci.Buffer.Length, SocketFlags.None, new AsyncCallback(Receive), client);
                    }
                }

                serversock.BeginAccept(new AsyncCallback(Accept), serversock);
            }
            catch (ObjectDisposedException e) { }
            catch (Exception e)
            {
                if (OnError != null)
                {
                    OnError("Accept", e.Message);
                }
            }
        }

        // Callback for receive data
        private void Receive(IAsyncResult result)
        {
            try
            {
                Socket client = (Socket)result.AsyncState;
                int len = client.EndReceive(result);

                if (OnReceive != null)
                {
                    ClientInfo ci = OnReceive(client, len);
                    if (ci != null)
                    {
                        client.BeginReceive(ci.Buffer, 0, ci.Buffer.Length, SocketFlags.None, new AsyncCallback(Receive), client);
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        // Callback for send data
        private void Sent(IAsyncResult result)
        {
            Socket sock = (Socket)result.AsyncState;
            int len = sock.EndSend(result);

            if (OnSent != null)
            {
                OnSent(sock, len);
            }
        }

        // Send data
        public void Send(ClientInfo ci, int length)
        {
            Socket sock = ci.Socket;
            Send(sock, ci.Buffer, length);
        }

        public void Send(Socket socket, byte[] p, int length)
        {
            socket.BeginSend(p, 0, length, SocketFlags.None, new AsyncCallback(Sent), socket);
        }

        public void Send(Socket socket, string p)
        {
            byte[] data = Helpper.GetBytes(p);
            Send(socket, data, data.Length);
        }

        // Close server
        public void Close()
        {
            //socket.Shutdown(SocketShutdown.Both);
            socket.Close();
        }

        public void SendListOfUsers(Socket socket)
        {
            StringBuilder sb = new StringBuilder();

            sb.Append("~~~~:");
            foreach (var item in ClientInfo.GetInstance().ClientList)
            {
                var c = item.Value;

                if (!c.UserName.Equals(""))
                {
                    sb.Append(c.UserName);
                    sb.Append(":");
                }
            }

            if (sb.Length > 0)
            {
                sb.Remove(sb.Length - 1, 1);
            }

            Send(socket, sb.ToString());
        }

        public void ResponseOk(Socket socket)
        {
            Send(socket, "Ok");
        }

        public void SendMessage(string un, string ms)
        {
            foreach (var item in ClientInfo.GetInstance().ClientList)
            {
                var c = item.Value;

                if (c.UserName.Equals(un))
                {
                    Send(c.Socket, un + ":" + ms);
                    break;
                }
            }
        }
    }
}
